package com.myCommon.myRedisLock;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class RedisLock implements Lock {
      
      private RedisTemplate redisTemplate;
      
      private String lockName;
      private String uuidThread = UUID.randomUUID().toString().replace("-", "") + Thread.currentThread().getId();
      private Integer expire=30;
      
      public RedisLock(RedisTemplate redisTemplate, String lockName) {
            this.redisTemplate = redisTemplate;
            this.lockName = lockName;
      }
      
      @Override
      public void lock() {
            tryLock();
      }
      
      @Override
      public boolean tryLock() {
            try {
                  tryLock(-1, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                  e.printStackTrace();
            }
            return false;
      }
      
      @Override
      public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            //考虑锁的可重入性  使用hash结构来代替setnx
            String script = "if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 then" +
                  "redis.call('hincryby',KEYS[1],ARGV[1],1)" +
                  "redis.call('expire',KEYS[1],ARGV[2])" +
                  "return 1" +
                  "else " +
                  "return 0 " +
                  "end";
            //使用自旋代替递归    递归可能会造成方法反复调用 导致栈溢出StackOverflowError
            boolean execute = (boolean) redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Arrays.asList(lockName), uuidThread, expire);
            while (!execute) {
                  TimeUnit.MICROSECONDS.sleep(20);
            }
            //锁的续期
            resetExpire();
            return true;
      }
      
      private void resetExpire() {
            new Timer().schedule(new TimerTask() {
                  @Override
                  public void run() {
                        String script = "if redis.call('hexists',KEYS[1],ARGV[1]==1 then" +
                              "return redis.call('expire','KEYS[1]',ARGV[2])" +
                              "else" +
                              "return 0 " +
                              "end";
                        if ((Boolean) redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Arrays.asList(lockName), uuidThread, expire)) {
                              resetExpire();
                        }
                  }
            },(this.expire*1000)/3);
      }
      
      @Override
      public void unlock() {
            //解决锁误删问题  如果不具备原子性还是有误删的风险
            String script = "if redis.call('hexists',KEYS[1],ARGV[1])==0 then" +
                  "return nil " +
                  "elseif redis.call('hincyby',KEYS[1],ARGV[1],-1)==0 then " +
                  "redis.call('del',KEYS[1])" +
                  "else" +
                  "return 0 " +
                  "end";
            Long execute = (Long) redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockName), uuidThread);
            if (null == execute) {
                  throw new RuntimeException("锁不存在,解锁失败");
            }
            }
      
      @Override
      public Condition newCondition() {
            return null;
      }
      
      @Override
      public void lockInterruptibly() throws InterruptedException {
      
      }
}
